(function( $ ){

  /* TABLE CONTROLLER CLASS */
  // makes it easier to separate logic from "doing what you're told".
  // Might move to a separate file in the future
  function Table(selector, rows, cols, shipsHidden) {
    this.selector = selector;
    this.rows = rows;
    this.cols = cols;
    this.lastShadow = null;
    //$(this.selector).cell(0, 0).css('background-color','red');
    //Lets add some data to all fields
    //Following should be moved to the server side!
    $(this.selector).find("td.field").data({"occupied": false,
                                           "bombed": false,
                                           "shiptype": null});
  };
  
  /* --- FUNCTIONS HANDLING GRAPHICS (low-level) --- */
  //Appends the given item as the background of cell located at (x, y) (zero-based)
  Table.prototype.addBackground = function(x, y, obj, layer) {
    var cell = $(this.selector).cell(x, y), content = cell.css('background-image');
    if (content === "none") {
      cell.css('background-image', obj);
    }
    else if (layer == 1) {
      cell.css('background-image', obj + ", " + content);
    }
    else {
      cell.css('background-image', content + ", " + obj);
    }
  };
  
  //removes all backgrounds
  Table.prototype.removeBackground = function(x, y){
    $(this.selector).cell(x, y).css('background-image', 'none');
  };
  
  /* --- FUNCTIONS HANDLING EVENTS (bombing, placing ships, etc) --- */
  Table.prototype.placeBomb = function(x, y, hit) {
    this.setBombed(x, y, true);
    if (hit) {
      this.addBackground(x, y, 'url("img/explosion.gif")', 1);
      playSound("explosion.ogg");      
    }
    else {
      this.addBackground(x, y, 'url("img/blank.png")', 1);
      playSound("miss.ogg");
    }
  };
  
  //returns true if hit a ship, false otherwise
  Table.prototype.bomb = function(x, y, reveal) {
    this.setBombed(x, y, true);
    if (this.getShipType(x, y) === null) {
        this.addBackground(x, y, 'url("img/blank.png")', 1);
        playSound("miss.ogg");
        return false;
    }
    else {
      var ship = this.gotAll(x, y);
      this.addBackground(x, y, 'url("img/explosion.gif")', 1);
      playSound("explosion.ogg");
      if (ship !== null && reveal) {
        //alert("adding ship!" + reveal);
        this.addShip(ship[0], ship[1], ship[2], ship[3]);
      }
      return true;
    }
  };
  
  //adds ship. automatically removes previous shadows.
  Table.prototype.addShip = function(x, y, length, horizontal, hide) {
    if (this.lastShadow !== null) {
      this.removeShadow();
    }
    for (var i = 0; i < length; i++) {
      if (horizontal) {
        this.setOccupied(x, y+i, true);
        this.setShipType(x, y+i, 'h' + length + (i+1));
        if (!hide) {
         this.addBackground(x, y+i, 'url("img/'+length+"x"+(i+1)+'H.png")', 0);
        }
      }
      else {
        this.setOccupied(x+i, y, true);
        this.setShipType(x+i, y, 'v' + length + (i+1));
        if (!hide) {
          this.addBackground(x+i, y, 'url("img/'+length+"x"+(i+1)+'V.png")', 0);
        }
      }
    }
  };
  
  Table.prototype.setEncoding = function(encoding) {
    var hor, x, y, l;
    // Reset field pictures
    for (x = 0; x < 10; x++)
      for (y = 0; y < 10; y++)
        this.removeBackground(x, y);
    // ...
    for (var i = 0; i < 40; i += 4) {
      hor = encoding[i] == "h";
      l = parseInt(encoding[i + 1]);
      x = parseInt(encoding[i + 2]);
      y = parseInt(encoding[i + 3]);
      this.addShip(x, y, l, hor, true);
    }
  };
  

  //removes ship from square.
  Table.prototype.removeShip = function(x, y, length, horizontal) {
    for (var i = 0; i < length; i++) {
      if (horizontal) {
        this.setOccupied(x, y+i, false);
        this.setShipType(x, y+i, null);
        this.removeBackground(x, y+i);
      }
      else {
        this.setOccupied(x+i, y, false);
        this.setShipType(x+i, y, null);
        this.removeBackground(x+i, y);
      }
    }
  };  
  
  Table.prototype.addShadow = function(x, y, length, horizontal) {
    if (this.lastShadow !== null) {
      this.removeShadow();
    }
    for (var i = 0; i < length; i++) {
      if (horizontal) 
        this.addBackground(x, y+i, 'url("img/'+length+"x"+(i+1)+'H.png")', 0);
      else
        this.addBackground(x+i, y, 'url("img/'+length+"x"+(i+1)+'V.png")', 0);
    }    
    this.lastShadow = [x, y, length, horizontal];
  };
  
  Table.prototype.removeShadow = function() {
    if (this.lastShadow === null) return;
    var x = this.lastShadow[0], y = this.lastShadow[1];
    var length = this.lastShadow[2], horizontal = this.lastShadow[3];
    for (var i = 0; i < length; i++) {
      if (horizontal)
        this.removeBackground(x, y+i);
      else
        this.removeBackground(x+i, y);
    }
    this.lastShadow = null;
  };
  
  Table.prototype.revealAll = function() {
    this.shipsHidden = false;
    var x, pos = this.getShipPos();
    for (x = 0; x < 10; x++)
      this.addShip(pos[x][0], pos[x][1], pos[x][2], pos[x][3]);
  };
  
  /* --- BASIC GETTERS/SETTERS --- some should be moved to server side */
  Table.prototype.isOccupied = function(x, y) {
    //returns if the given cell is occupied
    return $(this.selector).cell(x, y).data("occupied");
  };
  
  Table.prototype.isBombed = function(x, y) {
    //returns if the given cell has been bombed
    return $(this.selector).cell(x, y).data("bombed");
  };
  
  Table.prototype.setOccupied = function(x, y, val) {
    $(this.selector).cell(x, y).data("occupied", val);
  };
  
  Table.prototype.setBombed = function(x, y, val) {
    $(this.selector).cell(x, y).data("bombed", val);
  };
  
  Table.prototype.getShipType = function(x, y) {
    return $(this.selector).cell(x, y).data("shiptype");
  };
  
  //format: '(h|v)(length)(x)'
  Table.prototype.setShipType = function(x, y, val) {
    $(this.selector).cell(x, y).data("shiptype", val);
  };
  
  Table.prototype.getShipPos = function() {
    return $(this.selector).data("shipPositions");
  };
  
  Table.prototype.setShipPos = function(positions) {
    $(this.selector).data("shipPositions", positions);
  };
  
  //returns if placing a bomb here revealed the whole ship
  //if it did, returns [x, y, shiplength, horizontal]
  //if not, returns null
  Table.prototype.gotAll = function(x, y) {
    var sp = this.getShipType(x, y);
    var horizontal = sp[0] == 'h';
    var sx, sy, length = parseInt(sp[1]);
    if (horizontal) {
      sx = x;
      sy = y - parseInt(sp[2]) + 1;
    }
    else {
      sx = x - parseInt(sp[2]) + 1;
      sy = y;
    }
    for (var i = 0; i < length; i++) {
      if (horizontal && !this.isBombed(sx, sy+i)) {
        return null;
      }
      else if (!horizontal && !this.isBombed(sx+i, sy)) {
        return null;
      }
    }
    return [sx, sy, length, horizontal];
  };
  
  //returns if on this table all ships have been bombed!
  Table.prototype.lost = function() {
    var x, y;
    for (x = 0; x < this.rows; x++) {
      for (y = 0; y < this.cols; y++) {
        if (this.isOccupied(x, y) && !this.isBombed(x, y))
          return false;
      }
    }
    return true;
  };
  
  //Returns the encoding of the field
  Table.prototype.tableEncode = function() {
    var x, y, encoding = "";
    for (x = 0; x < this.rows; x++)
      for (y = 0; y < this.cols; y++) {
        var st = this.getShipType(x, y);
        //a ship and the first piece of a ship
        if (st != null && st[2] == '1') 
          encoding += st[0] + st[1] + x + y;
      }
    return encoding;
  };
  
  /* BOOLEAN FUNCTIONS (can place somewhere type) */
  //returns if you can place a ship at that position  
  Table.prototype.canPlace = function(x, y, length, horizontal) {
    //check bounds
    if (horizontal && (y + length > this.cols)) {
      return false;
    }
    if (!horizontal && (x + length > this.rows)) {
      return false;
    }
    return !this.conflict(x,y,length,horizontal);
  };
  
  //returns if there is a conflict placing a ship there 
  Table.prototype.conflict = function(x, y, length, horizontal) {
    //(doesn't check against bounds, only against other ships being too near)
    //to check we basically draw a box around the ship and check if every square is free
    var ax, ay, d1, d2;
    //d1 - moving accross the ship (from -1 to length+1)
    //d2 - moving from side to side (from -1 to +1)
    for (d1 = -1; d1 <= length; d1++) {
      for (d2 = -1; d2 <= 1; d2++) {
        if (horizontal) {
          ax = x + d2; ay = y + d1;
        }
        else {
          ax = x + d1; ay = y + d2;
        }
        //first check if the place is in bounds
        //then check if the place is not marked occupied
        if (ax >= 0 && ay >= 0 && ax < this.rows && ay < this.cols && this.isOccupied(ax, ay)) {
          return true;
        }
      }
    }
    return false;
  };
  
  /* GENERATING THE FIELD */
  Table.prototype.generateField = function(lengths, shipLength) {
    this.shipsHidden = true;
    var x, y, hor, pos = [];
    for (var i = 0; i < lengths; i++) {
      while (true) {
        x = Math.floor(Math.random() * this.rows);
        y = Math.floor(Math.random() * this.cols);
        hor = Math.floor(Math.random() * 2) == 1;
        // interesting - howcome we can access this from here?
        if (this.canPlace(x, y, shipLength[i], hor)) {
          this.addShip(x, y, shipLength[i], hor);
          pos.push([x, y, shipLength[i], hor]);
          //alert(x + " " + y + " " + shipLength[i] + " " + (hor));
          break;
        }
      }
    }
    this.setShipPos(pos);
    this.shipsHidden = false;
  };
  
  window.Table = Table;
})( jQuery );